#include "Terrain/TerrainSerializer.h"
#include <filesystem>
#include <fstream>
#include <ostream>

#include "Log.h"
#include "GameSingleton.h"
#include "GameThreadPool.hpp"

#include "Managers/GameManager.h"

#include "Utils/Utils.h"
#include "Utils/BinaryIO.h"


using namespace PocketSurvival::Engine;

namespace PocketSurvival
{

    /*
        地形文件 xx.map 内部解析格式

        1. 文件头(4byte): maps 
            可以通过这个判断地形的存储版本，然后进行特定的修改

        2. 256个区块的压缩数据大小(256*2 byte)
            按顺序保存对应区块的数据大小，没有加载的区块在这里大小为0

        3. 区块数据
            按照 2 所得到得数据大小依次读取即为对应得区块数据
            区块数据是以 TerrainChunk -> TerrainData -> terrainBuff 直接压缩得到
    */

    TerrainSerializer::TerrainSerializer()
    {
        createRegionCatch(16);
    }
    
    TerrainSerializer::~TerrainSerializer()
    {
        for(int32_t index = 0; index < m_regionCatchVector.size(); index++)
        {
            delete[] m_regionCatchVector[index];
        }
    }

    /***********************************************************************************************************
     * @brief 获取空的 地图区域 ，可用的 地图区域 队列中已经没有 地图区域 的话会向堆申请
     * @return nullptr 内存不足
    ***********************************************************************************************************/
    Region* TerrainSerializer::getNoDataRegion()
    {
        Region *avaRegion = nullptr;
        if(m_regionQueue.try_dequeue(avaRegion) == false)
        {
            createRegionCatch(16);

            // 增加了缓存都拿不到可用的对象的话，意味着内存用完了
            if(m_regionQueue.try_dequeue(avaRegion) == false)
            {
                return nullptr;
            }
        }
        if(avaRegion == nullptr)
        {
            Log::Error("发生严重错误： 从队列里面取出的 地图区域 对象为空，现在服务器疑似内存泄漏，请尽快重启", Log::LogType::TERRAIN);
            return nullptr;
        }

        // 拿到数据要先清理一遍，免得跟就数据其冲突
        avaRegion->clearAll();
        return avaRegion;
    }

    /***********************************************************************************************************
     * @brief 获取地图区域的块数据，若在地图区域表中不存在的会自动补充进地图区域表里面
     * @param x 地图区域对应的坐标
     * @param y 地图区域对应的坐标
     * @return 非nullptr即为地图区域对象 | nullptr 表示内存不足
    ***********************************************************************************************************/
    Region* TerrainSerializer::getRegion(int32_t x, int32_t y)
    {
        std::lock_guard<std::mutex> lk(m_regionMapMutex);

        Byte8Data key;
        key.a = x;
        key.b = y;

        // 先从哈希表里面查找有没有 地图区域 对象
        if(m_regionMap.find(key.int64_value) != m_regionMap.end())
        {
            return m_regionMap[key.int64_value];
        }
        std::string regionFileName = fmt::format("{}/Region {},{}.map", GameSingleton::gameManager->getNowWorldMapsPath(), x, y);

        // 哈希表找不到的话，查找有没有对应的文件数据，有的话就将文件数据加载进哈希表里面，并返回 地图区域 对象
        if(std::filesystem::exists(regionFileName) == true)
        {
            Log::Info(fmt::format("加载地图文件 {}", regionFileName), Log::LogType::TERRAIN);
            std::ifstream inStream(regionFileName, std::ios::in | std::ios::binary);
            if(inStream.is_open() == false)
            {
                inStream.close();
                return nullptr;
            }

            Utils::StreamBinaryReader streamReader((std::istream *)(&inStream));

            Region *avaRegion = getNoDataRegion();
            if(avaRegion == nullptr)
            {
                return nullptr;
            }

            // 读取掉头部的 Maps
            streamReader.readInt32();
            for(int32_t index = 0; index < 256; index++)
            {
                avaRegion->regionChunkSize[index] = streamReader.readUInt16();
                if(avaRegion->regionChunkSize[index] > MAP_CHUNK_SIZE)
                {
                    Log::Error(fmt::format("发现 地图区域 大于 {} 的情况，在地形文件: {} 中的 {}", MAP_CHUNK_SIZE, regionFileName,index), Log::LogType::TERRAIN);
                }
            }
            for(int32_t index = 0; index < 256; index++)
            {
                if(avaRegion->regionChunkSize[index] > 0)
                {
                    char *buffer = (char *)avaRegion->getChunkBuff(index);
                    streamReader.read(buffer, avaRegion->regionChunkSize[index]);
                }
            }

            inStream.close();

            m_regionMap[key.int64_value] = avaRegion;            
            return avaRegion;
        }

        // 若不存在 地图区域文件，那么直接使用一个空 地图区域 即可
        Region *avaRegion = getNoDataRegion();
        if(avaRegion != nullptr)
        {
            m_regionMap[key.int64_value] = avaRegion;  
            return avaRegion;
        }
        return nullptr;
    }

    /***********************************************************************************************************
     * @brief 向 地图区域 中读取 区块 的数据，读取前应该先赋值 区块 中的m_Coords数据
     * @param terrainChunk 区块对象
     * @return true 加载成功 | false 加载失败
    ***********************************************************************************************************/
    bool TerrainSerializer::loadChunk(TerrainChunk *terrainChunk)
    {
        if(terrainChunk == nullptr)
        {
            return false;
        }
        int32_t regionX = terrainChunk->coords.posX >> 4;
        int32_t regionY = terrainChunk->coords.posY >> 4;
        Region *theRegion = getRegion(regionX, regionY);
        if(theRegion == nullptr)
        {
            return false;
        }
        uint8_t chunkX = terrainChunk->coords.posX & 0xF;
        uint8_t chunkY = terrainChunk->coords.posY & 0xF;
        int32_t chunkSize = theRegion->getChunkSize(chunkX, chunkY);
        if(chunkSize <= 0)
        {
            return false;
        }
        
        char * buffer = (char *)terrainChunk->terrainData.terrainBuff;
        uint32_t buffSize = sizeof(terrainChunk->terrainData.terrainBuff);
        const char *chunkData = (const char*)theRegion->getChunkBuff(chunkX, chunkY);
        uint32_t outSize = Utils::RawInflateDecompresse(chunkData, chunkSize, buffer, buffSize);
        if(outSize <= 0)
        {
            Log::Error(fmt::format("解压 地图区域 {},{}出错了, 目标区块: {},{}, chunkSize: {}",terrainChunk->coords.posX, terrainChunk->coords.posY, chunkX,chunkY, chunkSize), Log::LogType::TERRAIN);
            return false;
        }
        return true;
    }

    /***********************************************************************************************************
     * @brief 将 区块 保存进对应 地图区域 内
     * @param terrainChunk 区块对象
     * @return true 保存成功 | false 保存失败
    ***********************************************************************************************************/
    bool TerrainSerializer::saveChunk(TerrainChunk *terrainChunk)
    {
        if(terrainChunk == nullptr)
        {
            return false;
        }
        int32_t regionX = terrainChunk->coords.posX >> 4;
        int32_t regionY = terrainChunk->coords.posY >> 4;
        Region *theRegion = getRegion(regionX, regionY);
        if(theRegion == nullptr)
        {
            return false;
        }
        uint8_t chunkX = terrainChunk->coords.posX & 0xF;
        uint8_t chunkY = terrainChunk->coords.posY & 0xF;
        
        char * chunkData = (char *)theRegion->getChunkBuff(chunkX, chunkY);
        uint32_t outSize = Utils::RawDeflateCompresse((const char *)terrainChunk->terrainData.terrainBuff, sizeof(terrainChunk->terrainData.terrainBuff), chunkData, MAP_CHUNK_SIZE);
        theRegion->setChunkSize(chunkX, chunkY, outSize);

        if(outSize <= 0)
        {
            Log::Error(fmt::format("压缩 地图区域 {},{}出错了",terrainChunk->coords.posX, terrainChunk->coords.posY), Log::LogType::TERRAIN);
            return false;
        }
        return true;
    }

    /***********************************************************************************************************
     * @brief 建所有在哈希表中的 地图区域 保存进本地的map文件中，保存后不会清理哈希表中的 地图区域
    ***********************************************************************************************************/
    void TerrainSerializer::saveAllRegion()
    {
        std::lock_guard<std::mutex> lk(m_regionMapMutex);
        
        GameThreadPool *threadPool = GameSingleton::singletonPtr->getSingleton<GameThreadPool>();

        for(auto it = m_regionMap.begin(); it != m_regionMap.end(); ++it)
        {
            Byte8Data theKey;
            theKey.int64_value = it->first;
            Region *regionItem = it->second;

            // 多线程保存数据，外部要注意线程安全
            threadPool->push_task([theKey, regionItem, this]
            {
                // 先将数据保存到 xx.map.new 文件里面，再替换原有的文件，防止保存错误导致地形损坏
                std::string regionFileName = fmt::format("{}/Region {},{}.map", GameSingleton::gameManager->getNowWorldMapsPath(), theKey.a, theKey.b);
                std::string temporaryFileName = fmt::format("{}/Region {},{}.map.new", GameSingleton::gameManager->getNowWorldMapsPath(), theKey.a, theKey.b);
                
                std::ofstream outStream(temporaryFileName, std::ios::out | std::ios::binary);
                if(outStream.is_open())
                {
                    Utils::StreamBinaryWriter streamWriter((std::ostream *)(&outStream));
                    // 写入文件头
                    streamWriter.write("Maps",4);
                    for(int32_t index = 0; index < 256; index++)
                    {
                        streamWriter.writeUInt16(regionItem->getChunkSize(index));
                    }
                    for(int32_t index = 0; index < 256; index++)
                    {
                        streamWriter.write((const char *)regionItem->getChunkBuff(index), regionItem->getChunkSize(index));
                    }
                    outStream.flush();
                    outStream.close();

                    if(std::filesystem::exists(regionFileName) == true && std::filesystem::remove(regionFileName) == false)
                    {
                        Log::Error(fmt::format("删除旧 地图区域 文件 {} 失败", temporaryFileName), Log::LogType::TERRAIN);
                    }
                    std::filesystem::rename(temporaryFileName, regionFileName);
                }
                else
                {
                    Log::Error(fmt::format("保存 地图区域 文件到 {} 失败", temporaryFileName), Log::LogType::TERRAIN);
                    outStream.close();
                }
            });
        }
    }
    
    /***********************************************************************************************************
     * @brief 回收所有在哈希表中的 地图区域，并将地图区域放进可用的 地图区域 队列中，注意，该函数不会对 地图区域 进行保存
    ***********************************************************************************************************/
    void TerrainSerializer::releaseAllRegion()
    {
        std::lock_guard<std::mutex> lk(m_regionMapMutex);

        int32_t num = 0;
        for(auto it = m_regionMap.begin(); it != m_regionMap.end(); ++it)
        {
            if(it->second != nullptr)
            {
                m_regionQueue.enqueue(it->second);
                num++;
            }
        }
        Log::Info(fmt::format("回收了 {} 个 地图区域", num), Log::LogType::TERRAIN);
        m_regionMap.clear();
    }

    /***********************************************************************************************************
     * @brief 保存和回收所有地图区域
    ***********************************************************************************************************/
    void TerrainSerializer::saveAndReleaseAllRegion()
    {
        GameThreadPool *threadPool = GameSingleton::singletonPtr->getSingleton<GameThreadPool>();

        std::lock_guard<std::mutex> lk(m_regionMapMutex);

        for(auto it = m_regionMap.begin(); it != m_regionMap.end(); ++it)
        {
            Byte8Data theKey;
            theKey.int64_value = it->first;
            Region *regionItem = it->second;

            // 多线程保存数据，外部要注意线程安全
            threadPool->push_task([theKey, regionItem, this]
            {
                std::string regionFileName = fmt::format("{}/Region {},{}.map", GameSingleton::gameManager->getNowWorldMapsPath(), theKey.a, theKey.b);
                std::string temporaryFileName = fmt::format("{}/Region {},{}.map.new", GameSingleton::gameManager->getNowWorldMapsPath(), theKey.a, theKey.b);
                std::ofstream outStream(temporaryFileName, std::ios::out | std::ios::binary);
                if(outStream.is_open())
                {
                    Utils::StreamBinaryWriter streamWriter((std::ostream *)(&outStream));
                    // 写入文件头
                    streamWriter.write("Maps",4);
                    for(int32_t index = 0; index < 256; index++)
                    {
                        streamWriter.writeUInt16(regionItem->getChunkSize(index));
                    }
                    for(int32_t index = 0; index < 256; index++)
                    {
                        streamWriter.write((const char *)regionItem->getChunkBuff(index), regionItem->getChunkSize(index));
                    }
                    outStream.flush();
                    outStream.close();

                    if(std::filesystem::exists(regionFileName) == true && std::filesystem::remove(regionFileName) == false)
                    {
                        Log::Error(fmt::format("删除旧 地图区域 文件 {} 失败", temporaryFileName), Log::LogType::TERRAIN);
                    }
                    std::filesystem::rename(temporaryFileName, regionFileName);
                }
                else
                {
                    Log::Error(fmt::format("保存 地图区域 文件到 {} 失败", temporaryFileName), Log::LogType::TERRAIN);
                    outStream.close();
                }
                m_regionQueue.enqueue(regionItem);
            });
        }
        m_regionMap.clear();
    }

    /***********************************************************************************************************
     * @brief 创建 地图区域 缓存，创建出来的 地图区域 缓存会被放进可用的 地图区域 队列中
     * @param count 要创建的 地图区域 缓存数量
    ***********************************************************************************************************/
    void TerrainSerializer::createRegionCatch(uint8_t count)
    {
        Log::Info(fmt::format("申请了 {} 个 地图区域 的缓存", count), Log::LogType::TERRAIN);

        Region *regionCatch = new Region[count];
        m_regionCatchVector.push_back(regionCatch);
        for(int32_t index = 0; index < count; index++)
        {
            m_regionQueue.enqueue(&(regionCatch[index]));
        }
    }
}